# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
#
# Copyright (C) 2007 - Łukasz Wiśniewski
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.

import clutter
import math
import gtk, gobject

from ClutterNotify import Notify

NO_COVER = 'rhythmbox-missing-artwork.png'
PAUSE = 'rhythmbox-pause.svg'
NOTIFY_BG = 'notify-bg.svg'

def FitToStage(actor, stage):
	(w, h) = actor.get_size()
	stage_w = stage.get_width()
	stage_h = stage.get_height()
	if stage_w < stage_h :
		aspect_ratio = stage_w/float(w)
		if h*aspect_ratio > stage_h :
			aspect_ratio *= stage_h/float(h*aspect_ratio)
	else:
		aspect_ratio = stage_h/float(h)
		if w*aspect_ratio > stage_w :
			aspect_ratio *= stage_w/float(w*aspect_ratio)
	actor.set_size(int(w*aspect_ratio), int(h*aspect_ratio))

def Center(actor,stage):
	(w, h) = actor.get_size()
	x = stage.get_width() / 2 - w / 2
	y = stage.get_height() / 2  - h / 2
	actor.set_position (int(x), int(y))

class Texture (clutter.Texture):
	__gsignals__ = {
		'button-press-event': (gobject.SIGNAL_RUN_FIRST, None, ())
	}

	def __init__ (self,pixbuf):
		clutter.Texture.__init__(self)
		self.set_pixbuf(pixbuf)
		
	def on_button_press(self):
		self.emit('button-press-event')


class Button (Texture):
	# future : implement mouse-over effect when enter-event, leave-event are merged mainstream
	def __init__ (self,path):
		Texture.__init__(self,path)
		
		# TOCONSIDER: make (class Effect) <-- (class PulseEffect)
		self.pulse_timeline = clutter.Timeline(120,60)
		self.pulse_timeline.set_loop(True)
		self.pulse_alpha = clutter.Alpha(self.pulse_timeline,clutter.sine_func)
		self.pulse_behave = clutter.BehaviourOpacity(self.pulse_alpha,50,150)
		self.pulse_behave.apply(self)

class Cover (Texture):
	def __init__ (self, pixbuf, entry=None):
		Texture.__init__(self,pixbuf)
		self.entry = entry
		
	def set_entry(self, entry):
		self.entry = entry
		
	def get_entry(self):
		return self.entry
		

				
class CoverManager(clutter.Group):
	def __init__ (self, default_pixbuf, stage):
		clutter.Group.__init__(self)
		# Once multiple stages are introduced this will be usefull
		# unless some per actor stage detection is introduced as well
		self.stage = stage
		
		self.default_pixbuf = default_pixbuf
		self.covers = []
		self.covers_signals = []
		
		self.timeline = clutter.Timeline(120, 60)
		self.timeline.set_loop(False)
		self.timeline.connect('new-frame', self.on_new_frame)
		self.timeline.connect('completed', self.on_completed)
		
		self.add(None)
	
	def add(self, pixbuf, entry=None):
		self.timeline.stop()
		if not pixbuf == None:
			self.covers.append(Cover(pixbuf, entry))
		else:
			self.covers.append(Cover(self.default_pixbuf, entry))
			
		self.covers_signals.append(self.covers[len(self.covers)-1].connect('button-press-event', self.on_cover_press))
		clutter.Group.add(self,self.covers[len(self.covers)-1])
		self.adjust(len(self.covers)-1)
		self.timeline.start()
		
	def remove(self,i):
		cover = self.covers.pop(i)
		cover_signal = self.covers_signals.pop(i)
		cover.disconnect (cover_signal)
		clutter.Group.remove(self,cover)
		cover.destroy()
		
	def adjust(self,i):
		FitToStage(self.covers[i],self.stage)
		Center(self.covers[i],self.stage)
	
	def on_new_frame (self, tl, frame_num):
		progress = float(frame_num)/tl.get_n_frames ()
		progress = (math.sin((progress-0.5)*math.pi)+1)/2	# 0->1 shift from linear to sinuoid
		
		# There are at least two covers, we need to perform eye-candy change
		if len(self.covers)>=2:
			for i in range(0,2):
				if i == 0:
					self.covers[i].set_opacity(int(255*(1-progress)))
				if i == 1:
					self.covers[i].set_opacity(int(255*progress))
				self.covers[i].show()
		else:
			(w, h) = self.covers[0].get_size()
			self.covers[0].show()
			self.covers[0].rotate_y(360*progress, w/2, 0)
		
	def on_completed (self, tl):
		# FIXME: this is hackish workaround for cover multi-add, won't work with multi-cover interfaces in the future
		while len(self.covers)>1:
			self.remove(0)
		if len(self.covers) == 1:
			self.covers[0].show()
			
	def get_current (self):
		return self.covers[0]
		
	def on_cover_press (self, cover):
		if not self.timeline.is_playing():
			self.timeline.start()
			
class ClutterMediator (clutter.Group):
	__gsignals__ = {
		'resume-playing': (gobject.SIGNAL_RUN_LAST, None, ()),
		'is-same-album': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN, (object, object,)),
		'get-pixbuf': (gobject.SIGNAL_RUN_LAST, None, (object, object,)),
		'get-song-info': (gobject.SIGNAL_RUN_LAST, object, (object,)),
	}

	def __init__ (self,stage,path):
		clutter.Group.__init__(self)
			
		pixbuf = gtk.gdk.pixbuf_new_from_file(path+'/'+NO_COVER)
			
		self.stage = stage
		self.stage.set_size(200, 200)
		self.stage.connect('button-press-event', self.on_button_press)
		
		self.covermanager = CoverManager(pixbuf, stage)
		self.covermanager.show()
		self.stage.add(self.covermanager)
		
		# Interface (future), this is just a prototype	
		self.song_notify = Notify(gtk.gdk.pixbuf_new_from_file(path+'/'+NOTIFY_BG))
		self.stage.add(self.song_notify)
		Center(self.song_notify, self.stage)
		self.song_notify.attach_actor(self.covermanager)
		
		self.pause_button = Button(gtk.gdk.pixbuf_new_from_file(path+'/'+PAUSE))
		self.pause_button.connect('button-press-event', self.on_pause_button_press)
		self.stage.add(self.pause_button)
		Center(self.pause_button, self.stage)
		
	def set_style(self,style):
		r = int(style.bg[gtk.STATE_NORMAL].red)
		g = int(style.bg[gtk.STATE_NORMAL].green)
		b = int(style.bg[gtk.STATE_NORMAL].blue)
		self.stage.set_color(clutter.Color(r, g, b, 0xff))
		
	def set (self, entry):
		# [!!] This concept is Rhythmbox oriented, needs more generic solution for the future
		if not self.emit('is-same-album', self.covermanager.get_current().get_entry(), entry):
			self.emit('get-pixbuf', entry, self.on_get_pixbuf_completed)
		else:
			self.covermanager.get_current().set_entry(entry)
			self.spin()
		#Updating notify on entry change
		self.song_notify.set_text( self.emit('get-song-info', entry) )
		
	def on_get_pixbuf_completed (self, entry, pixbuf=None, uri=False):
		# [!!] This concept is Rhythmbox oriented, needs more generic solution for the future
		# TODO: check if entry did not change in the meanwhile, supposedly multi-add may be a problem with one timeline
		self.covermanager.add(pixbuf, entry)

	def spin (self):
		if not self.covermanager.timeline.is_playing():
			self.covermanager.timeline.start()
			
	def on_button_press (self, stage, event):
		if (event.button != 1):
			self.song_notify.swap_visibility()
			return

		actor = stage.get_actor_at_pos(event.x, event.y)
		# The other approach would be every actor having method returning its type ("switch" model, filtering or whatever)
		# I prefer the "try" model, less LOC & easier maintance, should not hurt the performance too much...
		# and hey, event handling in clutter is "in the flux atm" so some changes in this code should be expected
		try:
			actor.on_button_press()
		except Exception:
			pass

	def on_pause_button_press(self, event):
		self.emit('resume-playing')
